#!/usr/bin/python
#-*- coding: utf-8 -*-
#
# Copyleft 2011 Orcun Avsar <orc.avs@gmail.com>
#
# GNU General Public Licence (GPL)
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


import time
import threading
import Queue
import warnings


DEFAULT_ERROR_HANDLERS = []

def add_default_error_handler(error_class, handler):
  if not (error_class, handler) in DEFAULT_ERROR_HANDLERS:
    DEFAULT_ERROR_HANDLERS.append((error_class, handler))


def remove_default_error_handler(error_class, handler):
  if (error_class, handler) in DEFAULT_ERROR_HANDLERS:
    DEFAULT_ERROR_HANDLERS.remove((error_class, handler))


class ReservedFunctionError(Exception):

  msg = "This function is reserved for BaseThread, use a different name."

  def __str__(self):
    return repr(self.msg)


class QuestionThread(threading.Thread):
  """ Base Thread for task based threads.
  """

  RESERVED_FUNCTIONS = ["stop", "run", "start", "question"]

  def __init__(self):
    super(QuestionThread, self).__init__()
    self._in = Queue.Queue()
    self._continue = True
    self._error_handlers = []

  def addErrorHandler(self, error_class, handler):
    if not (error_class, handler) in self._error_handlers:
      self._error_handlers.append((error_class, handler))

  def removeErrorHandler(self, error_class, handler):
    if (error_class, handler) in self._error_handlers:
      self._error_handlers.remove((error_class, handler))

  def question(self, fun_name, args=[], kwargs={}, reply_handler=None,
               error_handler=None, delay=0):
    """ Build a lambda function that is ready to call without arguments
    , put function to queue to be processed in 'run' function.

    Args:
      fun_name: the name of function to be put into the queue
      args: the arguments for the function in a tuple
      kwargs: the keyword arguments for the function in a dictionary
      reply_handler: the callback to be called after function
      error_handler: the callback to be called if function raises error
      delay: delay in seconds
    """

    if fun_name in self.RESERVED_FUNCTIONS:
      raise ReservedFunctionError

    call = lambda:getattr(self, fun_name)(*args, **kwargs)
    put = lambda:self._in.put((call, reply_handler, error_handler))
    timer = threading.Timer(delay, put)
    timer.start()

  def stop(self):
    """ Indicates thread needs to be terminated in next loop and puts
    some empty function to queue to iterate main loop once.
    """
    self._in.put((lambda:0, None, None))
    self._continue = False

  def run(self):
    while self._continue:
      call, reply_handler, error_handler = self._in.get()

      try:
        reply = call()
      except Exception as e:
        #Match error to appropriate handlers
        all_handlers = DEFAULT_ERROR_HANDLERS + self._error_handlers

        matched_handlers = []
        for error_class, handler in all_handlers:
          if (error_class == None or error_class == e.__class__):
            matched_handlers.append(handler)

        if error_handler:
          matched_handlers.append(error_handler)

        for handler in matched_handlers:
          handler(e, reply_handler)

        if not matched_handlers:
          warnings.warn("Exception is thrown away: %s:%s"
                        % (str(e.__class__), str(e)),
                        UserWarning)
      else:
        if reply_handler:
          reply_handler(reply)


class ThreadLimiter(object):

  slots = {}
  slots_int = {}
  limits = {}

  def setLimit(self, name, limit):
    self.limits[name] = limit

    item = Queue.Queue()
    for i in range(limit):
      item.put(1)
    self.slots[name] = item
    self.slots_int[name] = limit

  def get(self, name):
    self.slots[name].get()
    self.slots_int[name] -= 1

  def put(self, name):
    self.slots[name].put(1)
    self.slots_int[name] += 1

  def isFull(self, name):
    return self.limits[name] == self.slots_int[name] and True or False

  def isEmpty(self, name):
    return self.slots_int[name] == 0 and True or False

  def waitFull(self, name):
    while 1:
      if self.isFull(name):
        return
      time.sleep(1)

  def waitEmpty(self, name):
    while 1:
      if self.slots_int == 0:
        return
      time.sleep(1)


thread_limiter = ThreadLimiter()
